package classes;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Pattern;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import containers.Page;

public class PageParser implements Runnable {

	private BlockingQueue<Page> myPageQ;/**Queue for Urls going from the PR to the PP.*/
	private BlockingQueue<List<URL>> myUrlQ;/**Queue for Page objects going from the PP to the PR.*/
	private BlockingQueue<Page> myAnalQ;/**Queue for Page objects going from the PP to the PA.*/
	private int numProcessed; /**Total number of url's processed into page objects.*/
	private boolean okToProcess; /**Boolean control if ok to still process pages (numProcessed <= Test.PROCESS_LIMIT_10.*/
	private static final Pattern wordPattern = Pattern.compile("[\\w[-']]+"); //all word characters
	private static final String nonWordChar = new String("[\\W&&[^']]"); //non-word characters
	private boolean shouldStop; /**Control variable for early halt condition given from outside the program.*/
	
	public PageParser(final BlockingQueue<Page> pageQ, final BlockingQueue<List<URL>> urlQ, 
			final BlockingQueue<Page> analQ) {
		myPageQ = pageQ;
		myUrlQ = urlQ;
		myAnalQ = analQ;
		numProcessed = 0;
		okToProcess = true;
		shouldStop = false;
	}
	
	@Override
	public void run() {
		Page fromPR = null;
		
		//run thread while numProcessed is less than Test.PROCESS_LIMIT_10
		while(okToProcess) {
			//get next page from pageQ
			try {
				fromPR = myPageQ.take();
			} catch (InterruptedException e) {}
			
			//process
			if(!shouldStop) {
			
				List<String> links = new ArrayList<String>(); //links to retriever
				
				parsePage(fromPR, links); //synchronized
				
				//add page obj to analQ
		        addPageToAnalQ(fromPR);
				
				numProcessed++;
				System.out.print(",");
				
				//add links to urlQ
				if (numProcessed < Test.PROCESS_LIMIT) {
					addToUrlQ(links);
				}
			}
			
			if (numProcessed >= Test.PROCESS_LIMIT || shouldStop) {
				okToProcess = false;
				myPageQ.clear();
				myUrlQ.clear();
			}
		}
	}
	
	public synchronized void parsePage(final Page fromPR, List<String> theLinks) {
        List<String> words = new ArrayList<String>();
		// timer variables
        long parseTime = 0;
		long startTime = 0;
        long finishTime = 0;
        int urlCount = 0;
        
        // start the timer
        Date startDate = new Date();
        startTime = startDate.getTime();
        
		Document doc = Jsoup.parse(fromPR.getContents());
		
		Elements links = doc.select("a[href]");

		//build collection of "valid" links
        for (Element link : links) { //build links and add them to the collection of links found in this page
        	String strLink = buildLink(fromPR.getURL(), link.attr("href"));	        	
        	if (strLink != null) { //validation happens in the PageRetriever
        		theLinks.add(strLink);
        		urlCount++;
        	}
        }

        //build collection of words
        //rough tokenization - may need optimizing
        String[] titleText = doc.title().split("\\s");
        for (String word : titleText) {
        	word = word.replaceAll(nonWordChar, "");
        	if (word.length() > 0) {
        		words.add(word);
        	}
        }
        String[] bodyText = doc.body().text().split("\\s");
        for (String word : bodyText) {
        	word = word.replaceAll(nonWordChar, "");
        	if (word.length() > 0) {
        		words.add(word);
        	}
        }	        
       
        fromPR.setWords(words);
        fromPR.setLinkNumber(urlCount);
        
        // stop the timer
        Date finishDate = new Date();
        finishTime = finishDate.getTime();
        parseTime += (finishTime - startTime)/1000; //milliseconds to seconds
        
        fromPR.setParseTime(parseTime);
	}
	
	/**
	 * Builds link from absolute path and relative link found in body of page..
	 * If link is absolute, returns link
	 * @param theAbsolutePath	The URL of the page that contains the link
	 * @param theLink			The link found in the page being parsed
	 * @return String			The absolute path to the linked page
	 */
	public String buildLink(final URL theAbsolutePath, final String theLink) {
		String absolutePath = new String(theAbsolutePath.toString());
		String pathToLink = theLink;
		boolean relative = false;
		boolean absPath = true;
		
		try {
			URL linkURL = new URL(theLink);
		} catch (MalformedURLException e) {
			relative = true;
		}
		if (!theAbsolutePath.getPath().isEmpty()) {
			absPath = false; //www.x.com/x/
		}
		
		if (relative == false) { //link is absolute
			pathToLink = theLink;
		} else if (theLink.startsWith("#")) { //anchor to current page
			pathToLink = absolutePath; //to count in statistics
		} else if (absPath == false) { //www.x.com/x.html || www.x.com/x.html/
			if (!absolutePath.endsWith("/")){ //www.x.com/x.html
				if (!theLink.startsWith("/") || !theLink.startsWith(".")) { //www.x.com/x.html & x/
					pathToLink = absolutePath.substring(0, absolutePath.lastIndexOf("/") + 1) + theLink;
				} else { //www.x.com/x.html & /x/
					pathToLink = absolutePath + theLink;
				}				
			} else { //www.x.com/x.html & /1
				pathToLink = absolutePath.substring(0, absolutePath.lastIndexOf("/") + 1) + theLink;
			}
		} else if (absPath == true) { //www.x.com/ or www.x.com
			if (!absolutePath.endsWith("/") && (!theLink.startsWith("/") || !theLink.startsWith("."))) { //www.x.com
				pathToLink = absolutePath + "/" + theLink; //this is for instances where both absolute and relative have no "/" between them				
			} else { //(www.x.com or www.x.com/) &  
				pathToLink = absolutePath + theLink;
			}
		} else { //concatenate absolute and relative
			pathToLink = absolutePath + theLink;
		}
		return pathToLink;
	}

	private void addToUrlQ(final List<String> strLinks) {
		List<URL> urls = new ArrayList<URL>();
		for (String link : strLinks) {
			URL currURL = null;
			try {
				currURL = new URL(link);
				urls.add(currURL);
			} catch (MalformedURLException e) {}
			
			try {
				myUrlQ.put(urls);
			} catch (InterruptedException e) {}
		}
	}
	
	private void addPageToAnalQ(final Page thePage) {
		try {
			myAnalQ.put(thePage);
		} catch (InterruptedException e) {}
	}
	
	public void stop() {
		shouldStop = true;
	}
}
